package com.ruoyi.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.PiPService;
import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.FmsMain;
import com.ruoyi.system.domain.FmsType;
import com.ruoyi.system.domain.vo.FmsMainVo;
import com.ruoyi.system.domain.vo.IncomeExpenditureStatistics;
import com.ruoyi.system.domain.vo.SumFmsMainMoney;
import com.ruoyi.system.mapper.FmsMainMapper;
import com.ruoyi.system.service.IFmsMainService;
import com.ruoyi.system.service.IFmsTypeService;
import com.ruoyi.system.service.IFutureService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import static org.springframework.beans.BeanUtils.copyProperties;

/**
 * 财务明细服务实现类
 *
 * @author ruoyi
 * @date 2023-12-31
 * @version 1.0
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class FmsMainServiceImpl extends ServiceImpl<FmsMainMapper, FmsMain> implements IFmsMainService {
    private final IFmsTypeService fmsTypeService;
    private final ThreadPoolExecutor threadPoolExecutor;
    private final IFutureService futureService;
    private final RedisCache redisCache;
    private final RedisUtils redisUtils;

    /**
     * 查询财务明细列表
     *
     * @param id 财务明细主键
     * @return 财务明细
     */
    @Override
    public FmsMainVo selectFmsMainById(Long id) {
        FmsMain fmsMain = this.getById(id);
        FmsMainVo fmsMainVo = new FmsMainVo();
        copyProperties(fmsMain, fmsMainVo);
        fmsMainVo.setMoneyTypeValue(fmsMain.getMoneyType().toString());
        return fmsMainVo;
    }

    /**
     * 查询财务明细列表
     * 通过异步任务编排查询
     *
     * @param fmsMain    财务明细
     * @return 财务明细
     */
    @Override
    public List<FmsMainVo> selectFmsMainList(FmsMainVo fmsMain) {
        LambdaQueryWrapper<FmsMain> wrapper = buildQueryWrapper(fmsMain);
        Page<FmsMain> page = new Page<>(TableSupport.getPageDomain().getPageNum(),
                TableSupport.getPageDomain().getPageSize());
        Page<FmsMain> resultPage = this.page(page, wrapper);

        List<FmsMain> records = resultPage.getRecords();
        if (!records.isEmpty()) {
            Map<Long, String> typeIdToNameMap = buildTypeNameMapping();
            List<FmsMainVo> result = convertToVoList(records, typeIdToNameMap);
            if (!result.isEmpty()) {
                result.get(0).setTotalCount(Math.toIntExact(resultPage.getTotal()));
            }
            return result;
        }
        return Collections.emptyList();
    }

    /**
     * 构建查询条件
     */
    private LambdaQueryWrapper<FmsMain> buildQueryWrapper(FmsMainVo fmsMain) {
        LambdaQueryWrapper<FmsMain> wrapper = new LambdaQueryWrapper<>();
        if (fmsMain.getMainMoney() != null) {
            wrapper.eq(FmsMain::getMainMoney, fmsMain.getMainMoney());
        }
        if (fmsMain.getTypeId() != null) {
            wrapper.eq(FmsMain::getTypeId, fmsMain.getTypeId());
        }
        wrapper.between(FmsMain::getCreateTime, DateUtils.getYearFirstDay(), DateUtils.getYearLastDay());
        wrapper.orderByDesc(FmsMain::getCreateTime);
        return wrapper;
    }

    /**
     * 构建类型ID到名称的映射
     */
    private Map<Long, String> buildTypeNameMapping() {
        List<FmsType> fmsTypes = fmsTypeService.selectFmsTypeAllList();
        return fmsTypes.stream()
                .collect(Collectors.toMap(FmsType::getId, FmsType::getTypeName,
                        (existing, replacement) -> existing));
    }

    /**
     * 转换为VO列表
     */
    private List<FmsMainVo> convertToVoList(List<FmsMain> fmsMains, Map<Long, String> typeIdToNameMap) {
        return Optional.ofNullable(fmsMains)
                .map(list -> list.stream()
                        .map(item -> convertToFmsMainVo(item, typeIdToNameMap))
                        .collect(Collectors.toList()))
                .orElse(Collections.emptyList());
    }

    /**
     * 单个实体转换为VO
     */
    private FmsMainVo convertToFmsMainVo(FmsMain item, Map<Long, String> typeIdToNameMap) {
        FmsMainVo vo = new FmsMainVo();
        vo.setId(item.getId());
        vo.setTypeId(item.getTypeId());
        vo.setMainMoney(item.getMainMoney());
        vo.setMoneyType(item.getMoneyType());
        vo.setCreateTime(item.getCreateTime());
        vo.setCreateBy(item.getCreateBy());
        vo.setCreateTimeNew(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", item.getCreateTime()));
        vo.setTypeName(typeIdToNameMap.getOrDefault(item.getTypeId(), ""));
        vo.setRemark(StringUtils.isEmpty(item.getRemark()) ? vo.getTypeName() : item.getRemark());
        vo.setMoneyTypeValue(item.getMoneyType().toString());
        vo.setMoneyTypeValueStr(item.getMoneyType() == 0 ? "收入" : "支出");
        return vo;
    }
    /**
     * 新增财务明细
     *
     * @param fmsMain 财务明细
     * @return 结果
     */
    @Override
    public boolean insertFmsMain(FmsMainVo fmsMain) {
        redisUtils.extracted();
        FmsMain fmsMain1 = setFmsMain(fmsMain);
        return this.save(fmsMain1);
    }

    private static FmsMain setFmsMain(FmsMainVo fmsMain) {
        FmsMain fmsMain1 = new FmsMain();
        copyProperties(fmsMain, fmsMain1);
        fmsMain1.setMoneyType(Integer.parseInt(fmsMain.getMoneyTypeValue()));
        return fmsMain1;
    }

    /**
     * 修改财务明细
     *
     * @param fmsMain 财务明细
     * @return 结果
     */
    @Override
    public Boolean updateFmsMain(FmsMainVo fmsMain) {
        redisUtils.extracted();
        FmsMain fmsMain1 = setFmsMain(fmsMain);
        return this.updateById(fmsMain1);
    }

    /**
     * 批量删除财务明细
     *
     * @param ids 需要删除的财务明细主键
     * @return 结果
     */
    @Override
    public boolean deleteFmsMainByIds(Long[] ids) {
        redisUtils.extracted();
        return this.removeByIds(Arrays.asList(ids));
    }

    /**
     * 删除财务明细信息
     *
     * @param id 财务明细主键
     * @return 结果
     */
    @Override
    public Boolean deleteFmsMainById(Long id) {
        redisUtils.extracted();
        return this.removeById(id);
    }

    /**
     * 根据类型选择FMS主数据金额（带缓存功能）
     *
     * <p>该方法首先尝试从Redis缓存中获取数据，若存在则直接返回缓存值；
     * 若不存在则从数据库查询，并将查询结果存入Redis缓存后返回。</p>
     *
     * @param type 财务类型标识符（非空）
     * @return 对应类型的金额汇总值（BigDecimal类型），不会返回null
     * @implNote 方法采用同步机制保证线程安全，缓存键格式为CacheConstants.FMS_CAPITAL + type
     */
    @Override
    public synchronized BigDecimal selectFmsMainByType(Integer type) {
        // 优先从Redis缓存查询
        if (redisCache.hasKey(CacheConstants.FMS_CAPITAL + type)) {
            return redisCache.getCacheObject(CacheConstants.FMS_CAPITAL + type);
        }

        // 缓存不存在时查询数据库并写入缓存
        BigDecimal mainByType = this.baseMapper.selectFmsMainByType(type);
        redisCache.setCacheObject(CacheConstants.FMS_CAPITAL + type, mainByType);
        return mainByType;
    }


    /**
     * 根据类型获取当月消费总金额（带缓存机制）
     *
     * <p>该方法首先尝试从Redis缓存中获取数据，若缓存不存在则从数据库查询，
     * 并将查询结果存入Redis缓存以供后续使用。</p>
     *
     * @param type 消费类型标识符
     *             (用于区分不同的消费类别，对应缓存和数据库查询条件)
     * @return 当月指定类型的消费总金额（BigDecimal类型）
     * @implNote 方法使用synchronized关键字保证线程安全，
     * 缓存键格式：CacheConstants.FMS_CAPITAL_OF_MONTH + type
     */
    @Override
    public synchronized BigDecimal selectFmsMainByTypeOfMonth(Integer type) {
        // 优先从Redis缓存获取数据
        if (redisCache.hasKey(CacheConstants.FMS_CAPITAL_OF_MONTH + type)) {
            return redisCache.getCacheObject(CacheConstants.FMS_CAPITAL_OF_MONTH + type);
        }

        // 缓存不存在时从数据库查询并设置缓存
        log.info("从数据库中获取当月消费总数");
        BigDecimal mainByTypeOfMonth = this.baseMapper.selectFmsMainByTypeOfMonth(type);
        redisCache.setCacheObject(CacheConstants.FMS_CAPITAL_OF_MONTH + type, mainByTypeOfMonth);
        return mainByTypeOfMonth;
    }


    /**
     * 根据消费类型查询本周消费总金额（带缓存机制）
     * <p>
     * 该方法首先尝试从Redis缓存中获取数据，若缓存未命中则从数据库查询，
     * 并将查询结果存入Redis缓存。方法使用synchronized保证线程安全。
     *
     * @param type 消费类型标识符
     * @return BigDecimal 本周对应消费类型的总金额
     */
    @Override
    public synchronized BigDecimal selectFmsMainByTypeOfWeek(Integer type) {
        // 优先从Redis缓存获取数据
        if (redisCache.hasKey(CacheConstants.FMS_CAPITAL_OF_WEEK + type)) {
            return redisCache.getCacheObject(CacheConstants.FMS_CAPITAL_OF_WEEK + type);
        }

        // 缓存未命中时从数据库查询并更新缓存
        log.info("从数据库中获取本周消费总数");
        BigDecimal mainByTypeOfWeek = this.baseMapper.selectFmsMainByTypeOfWeek(type);
        redisCache.setCacheObject(CacheConstants.FMS_CAPITAL_OF_WEEK + type, mainByTypeOfWeek);

        return mainByTypeOfWeek;  // 注意：原代码存在重复查询问题，此处应直接返回已查询结果
    }


    /**
     * 根据财务类型异步统计总金额（收入或支出）
     *
     * @param type 财务类型标识（1表示支出，其他值表示收入）
     * @return IncomeExpenditureStatistics 包含月统计、周统计和总金额的财务统计对象
     * 当发生异常时返回空统计对象
     */
    @Override
    public IncomeExpenditureStatistics asseFmsMoney(Integer type) {
        // 异步获取三种不同时间维度的财务数据
        CompletableFuture<BigDecimal> typeOfMonthFuture = futureService.getFmsMainByTypeOfMonth(this, type, threadPoolExecutor);
        CompletableFuture<BigDecimal> typeOfWeekFuture = futureService.getFmsMainByTypeOfWeek(this, type, threadPoolExecutor);
        CompletableFuture<BigDecimal> mainByTypeFuture = futureService.getFmsMainByTypeFuture(this, type, threadPoolExecutor);

        try {
            // 等待所有异步任务完成
            CompletableFuture.allOf(typeOfMonthFuture, typeOfWeekFuture, mainByTypeFuture).join();

            // 处理异步结果，null值转为0
            IncomeExpenditureStatistics statistics = new IncomeExpenditureStatistics();
            BigDecimal monthResult = typeOfMonthFuture.get() == null ? new BigDecimal("0.00") : typeOfMonthFuture.get();
            BigDecimal weekResult = typeOfWeekFuture.get() == null ? new BigDecimal("0.00") : typeOfWeekFuture.get();
            BigDecimal typeResult = mainByTypeFuture.get() == null ? new BigDecimal("0.00") : mainByTypeFuture.get();

            // 根据类型设置统计结果
            if (type == 1) {
                statistics.setTotalExpenditureMonth(monthResult);
                statistics.setTotalExpenditureWeek(weekResult);
                statistics.setTotalExpenditure(typeResult);
                statistics.setTotalIncome(BigDecimal.ZERO);
                statistics.setTotalIncomeMonth(BigDecimal.ZERO);
                statistics.setTotalIncomeWeek(BigDecimal.ZERO);
            } else {
                statistics.setTotalExpenditureMonth(BigDecimal.ZERO);
                statistics.setTotalExpenditureWeek(BigDecimal.ZERO);
                statistics.setTotalExpenditure(BigDecimal.ZERO);
                statistics.setTotalIncome(typeResult);
                statistics.setTotalIncomeMonth(monthResult);
                statistics.setTotalIncomeWeek(weekResult);
            }
            return statistics;
        } catch (InterruptedException | ExecutionException e) {
            log.error("异步查询财务数据出错", e);
            return new IncomeExpenditureStatistics();
        }
    }


    // 组装数据
    private List<PiPService.Series.Data> setPipData(Integer type) {
        List<SumFmsMainMoney> moneyList = baseMapper.selectSumMoneyBytype(type);
        List<FmsType> fmsTypes = fmsTypeService.selectFmsTypeAllList();
        return moneyList.stream().map(fmsMain -> {
            PiPService.Series.Data data = new PiPService.Series.Data();
            fmsTypes.stream().filter(fmsType -> Objects.equals(fmsType.getId(), fmsMain.getTypeId())).findFirst().ifPresent(fmsType -> data.setName(fmsType.getTypeName()));
            data.setValue(fmsMain.getMainMoney());
            return data;
        }).collect(Collectors.toList());
    }

    /**
     * 创建并配置图表项样式对象
     * <p>
     * 该方法创建一个PiPService图表系列的强调项样式对象(ItemStyle)，
     * 并设置其阴影效果参数：
     * - 阴影模糊度(shadowBlur)为10像素
     * - 阴影水平偏移(shadowOffsetX)为0（无水平偏移）
     * - 阴影颜色为半透明黑色(rgba格式)
     *
     * @return PiPService.Series.Emphasis.ItemStyle 配置完成的图表项样式对象
     */
    private PiPService.Series.Emphasis.ItemStyle setItemStyle() {
        PiPService.Series.Emphasis.ItemStyle itemStyle = new PiPService.Series.Emphasis.ItemStyle();
        // 配置阴影效果参数
        itemStyle.setShadowBlur(10);
        itemStyle.setShadowOffsetX(0);
        itemStyle.setShadowColor("rgba(0, 0, 0, 0.5)");
        return itemStyle;
    }


    /**
     * 设置饼图(Pie)的系列(Series)配置
     *
     * @param type 数据类型标识，用于区分不同的数据源或数据类别
     * @return List<PiPService.Series> 返回包含一个饼图系列配置的列表，
     * 该系列包含名称、类型、半径、数据以及强调样式等配置
     */
    private List<PiPService.Series> setSeries(Integer type) {
        // 初始化系列列表
        List<PiPService.Series> list = new ArrayList<PiPService.Series>();

        // 配置强调样式(Emphasis)
        PiPService.Series.Emphasis emphasis = new PiPService.Series.Emphasis();
        emphasis.setItemStyle(setItemStyle());

        // 创建并配置饼图系列
        PiPService.Series item = new PiPService.Series();
        item.setName("数据项");
        item.setType("pie");
        item.setRadius("50%");  // 设置饼图半径为容器宽度的一半
        item.setData(setPipData(type));  // 设置饼图数据，数据内容由type参数决定
        item.setEmphasis(emphasis);  // 设置强调样式

        list.add(item);
        return list;
    }

    // 设置标题
    private static PiPService.Title setTitle() {
        PiPService.Title title = new PiPService.Title();
        title.setText("财务数据统计");
        title.setSubtext("支出");
        title.setLeft("center");
        return title;
    }

    /**
     * 异步设置 PiPService 对象
     * <p>
     * 使用 CompletableFuture 并行执行以下任务：
     * 1. 设置标题(setTitle)
     * 2. 设置工具提示(setToolTip)
     * 3. 设置图例(setLegend)
     * 4. 设置系列数据(setSeries)
     * 所有任务完成后组装成完整的 PiPService 对象
     *
     * @param type 系列数据类型，用于确定要设置的系列数据内容
     * @return 组装完成的 PiPService 对象，如果发生异常则返回空 PiPService 对象
     */
    private PiPService setService(Integer type) {
        // 并行执行四个异步任务，使用线程池 threadPoolExecutor
        CompletableFuture<PiPService.Title> titleFuture = CompletableFuture.supplyAsync(FmsMainServiceImpl::setTitle, threadPoolExecutor);
        CompletableFuture<PiPService.Tooltip> tooltipFuture = CompletableFuture.supplyAsync(FmsMainServiceImpl::setToolTip, threadPoolExecutor);
        CompletableFuture<PiPService.Legend> legendFuture = CompletableFuture.supplyAsync(FmsMainServiceImpl::setLegend, threadPoolExecutor);
        CompletableFuture<List<PiPService.Series>> seriesFuture = CompletableFuture.supplyAsync(() -> setSeries(type), threadPoolExecutor);

        try {
            // 等待所有异步任务完成
            CompletableFuture.allOf(titleFuture, tooltipFuture, legendFuture, seriesFuture).join();

            // 组装 PiPService 对象
            PiPService piPService = new PiPService();
            piPService.setTitle(titleFuture.get());
            piPService.setTooltip(tooltipFuture.get());
            piPService.setLegend(legendFuture.get());
            piPService.setSeries(seriesFuture.get());
            return piPService;
        } catch (InterruptedException | ExecutionException e) {
            // 异步任务执行异常处理
            log.error("异步设置 PiPService 出错", e);
            return new PiPService();
        }
    }


    // 设置Legend
    private static PiPService.Legend setLegend() {
        PiPService.Legend legend = new PiPService.Legend();
        legend.setBottom(10);
        legend.setLeft("center");
        return legend;
    }

    // 设置ToolTip
    private static PiPService.Tooltip setToolTip() {
        PiPService.Tooltip tooltip = new PiPService.Tooltip();
        tooltip.setTrigger("item");
        return tooltip;
    }

    /**
     * 获取指定类型的PiP服务数据
     *
     * @param type 服务类型标识符，用于区分不同类型的PiP服务
     * @return PiPService 与指定类型对应的PiP服务对象
     */
    @Override
    public PiPService getPipData(Integer type) {
        return setService(type);
    }
}